// Author:
//   Jason Couture, plaguethenet@gmail.com
//
// Copyright (c) 2007-2011, Jason Couture
//
// All rights reserved.
//
// If you wish to use XML Settings in your application, YOU MUST FIRST request permission.
// This source is provided to the llAma project to ease the build process on linux systems.
// It may be modified and used with llAma only, And not in another project, or released on
// its own, Without my express written permssion. AKA, ALL RIGHTS RESERVED.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Xml;
using System.IO;
using System.Net;

namespace XMLSettings
{
    public class InvalidKeyException : Exception
    {
        public InvalidKeyException(string message)
            : base(message)
        { }
    }
    [Serializable()]
    public class Setting : IEnumerable<Setting>
    {
        private static readonly Setting _NullSetting = new Setting("null", string.Empty, null);
        bool _IsTemporary = false;
        public bool Temporary
        {
            get
            {
                return _IsTemporary;
            }
            set
            {
                _IsTemporary = value;
            }
        }
        //public delegate void Event<T>(object sender, T e);
        private event EventHandler<EventArgs> Modified;
        public event EventHandler<EventArgs> OnModify
        {
            add
            {
                if (this._Parent == null)
                    Modified += value;
                else
                    Root.Modified += value;
            }
            remove
            {
                if (this._Parent == null)
                    Modified -= value;
                else
                    Root.Modified -= value;
            }
        }

        public static Setting NullSetting
        {
            get
            {
                _NullSetting.Name = "null";
                _NullSetting.Clear();
                _NullSetting.Value = string.Empty;
                return _NullSetting;
            }
        }
        internal void WasModified(Setting Key)
        {
            if (Temporary) return;
            if (this._Parent == null)
            {
                if (Modified != null)
                {
                    Modified(Key, new EventArgs());
                }
            }
            else
            {
                _Parent.WasModified(Key);
            }
        }
        #region Private Variables
        internal Dictionary<string, Setting> _childList = new Dictionary<string, Setting>();
        private string _Name = @"";
        private string _Value = @"";
        private string _SavePath = @"";
        private string _BackupPath = @"";
        private Setting _Parent = null;
        internal object objLock = new object();
        private bool usecache = false;
        private FileAttributes BackupFA = FileAttributes.Hidden | FileAttributes.Archive;
        #endregion


        #region Static
        #region Functions
        private static Dictionary<string, Setting> mergeInternalDict(Setting Child1, Setting Child2)
        {
            Dictionary<string, Setting> retval = new Dictionary<string, Setting>();
            foreach (KeyValuePair<string, Setting> D in Child1._childList)
            {
                retval.Add(D.Key, D.Value);
            }
            foreach (KeyValuePair<string, Setting> D in Child2._childList)
            {
                if (retval.ContainsKey(D.Key))
                {
                    retval[D.Key] = D.Value;
                }
                else
                {
                    retval.Add(D.Key, D.Value);
                }
            }
            return retval;
        }
        #endregion
        #region Type Cast Overloads
        public static implicit operator bool(Setting S)
        {
            bool retval;
            bool.TryParse(S.Value, out retval);
            return retval;
        }
        public static implicit operator Setting(List<string> L)
        {
            Setting S = new Setting("temp");
            S["count"] = L.Count;
            for (int x = 0; x < L.Count; x++)
            {
                S[x] = L[x];
            }
            return S;
        }
        public static implicit operator List<string>(Setting S)
        {
            List<string> retval = new List<string>();
            int count = 0;
            count = S["count"];
            if (S["type"] != "")
                count = 0;
            for (int x = 0; x < count; x++)
            {
                retval.Add(S[x].Value);
            }
            return retval;
        }
        public static implicit operator Setting(bool b)
        {
            return new Setting("temp", b.ToString());
        }
        public static implicit operator DateTime(Setting S)
        {
            DateTime retval;
            DateTime.TryParse(S.Value, out retval);
            return retval;
        }
        public static implicit operator Setting(DateTime D)
        {
            Setting S = new Setting("temp", D.ToString());
            return S;
        }
        public static implicit operator int(Setting S)
        {
            int retval;
            int.TryParse(S.Value, out retval);
            return retval;
        }
        public static implicit operator Setting(int i)
        {
            Setting S = new Setting("temp", i.ToString());
            return S;
        }
		public static implicit operator Setting(long i)
		{
			Setting S = new Setting("temp", i.ToString());
			return S;
		}
		public static implicit operator long(Setting S)
		{
			long retval;
			long.TryParse(S.Value, out retval);
			return retval;
		}
        public static implicit operator IPAddress(Setting s)
        {
            IPAddress ret = IPAddress.None;
            IPAddress.TryParse(s.Value, out ret);
            return ret;
        }
        public static implicit operator Setting(IPAddress s)
        {
            Setting t = new Setting("temp");
            t.Value = s.ToString();
            return t;
        }
        public static implicit operator string(Setting S)
        {
            return S.Value;
        }
        public static implicit operator Setting(string S)
        {
            Setting T = new Setting();
            T.Name = "Temporary name.";
            T.Value = S;
            return T;
        }
        public static implicit operator Setting(Dictionary<string, string> dict)
        {
            Setting t = new Setting();
            foreach (KeyValuePair<string, string> kv in dict)
            {
                t[kv.Key] = kv.Value;
            }
            return t;
        }
        public static implicit operator Dictionary<string, string>(Setting s)
        {
            Dictionary<string, string> ret = new Dictionary<string, string>();
            foreach (Setting t in s)
            {
                ret[t.Name] = t.Value;
            }
            return ret;
        }
        #endregion
        #endregion


        #region Constructors
        public Setting()
        {
        }
        public Setting(string name)
        {
            _Name = name;
        }
        public Setting(string name, string value)
        {
            _Name = name;
            _Value = value;
        }
        public Setting(string name, string value, Setting parent)
        {
            _Name = name;
            _Value = value;
            _Parent = parent;
        }
        public Setting(string name, Setting parent)
        {
            _Name = name;
            _Parent = parent;
        }
        #endregion


        #region Private functions
        private void Save(XmlTextWriter XW)
        {
            if (IsDeadLeaf) return;
            XW.WriteStartElement("key");
            XW.WriteAttributeString("name", _Name);
            if (!string.IsNullOrEmpty(_Value))
                XW.WriteAttributeString("value", _Value);
            foreach (KeyValuePair<String, Setting> D in _childList)
            {
                D.Value.Save(XW);
            }
            XW.WriteEndElement();
        }
        private void Load(XmlNode XN)
        {
            _Name = XN.Attributes["name"].Value;
            if (XN.Attributes["value"] != null)
                _Value = XN.Attributes["value"].Value;
            else
                _Value = string.Empty;
            if (XN.HasChildNodes)
            {
                foreach (XmlNode X in XN.ChildNodes)
                {
                    Setting temp = this[X.Attributes["name"].Value];
                    temp.Load(X);
                    this[X.Attributes["name"].Value] = temp;
                }
            }
        }
        #endregion
        public Setting Parent
        {
            get
            {
                return _Parent;
            }
            private set
            {
                if (value == null && _Parent == null)
                    return;
                if ((_Parent == null) || (value == null))
                {
                    _Parent = value;
                    WasModified(this);
                }
                if (_Parent.GetHashCode() != value.GetHashCode())
                {
                    _Parent = value;
                    WasModified(this);
                }
            }
        }
        public Setting DeepCopy()
        {
            return Root.AsRoot;
        }
        #region Public functions
        public FileAttributes BackupFileAttributes
        {
            get { return BackupFA; }
            set { BackupFA = value; }
        }
        public string DefaultBackupPath
        {
            get
            {
                return Filename + ".old";
            }
        }
        public string BackupPath
        {
            get
            {
                if (string.IsNullOrEmpty(_BackupPath))
                    BackupPath = DefaultBackupPath;
                return _BackupPath;
            }
            set
            {
                DirectoryInfo di = new DirectoryInfo(System.IO.Path.GetDirectoryName(value));
                if (!di.Exists)
                    di.Create();
                FileInfo FI = new FileInfo(value);
                _BackupPath = FI.FullName;
            }
        }
        public bool HasChildren
        {
            get
            {
                return this._childList.Count == 0 ? false : true;
            }
        }
        public bool IsDeadLeaf
        {
            get
            {
                if (Temporary)
                    return true;
                if (HasChildren && string.IsNullOrEmpty(_Value))
                {
                    foreach (Setting s in this)
                    {
                        if (!s.IsDeadLeaf)
                            return false;
                    }
                    return true;
                }
                return (!HasChildren && string.IsNullOrEmpty(_Value));
            }
        }
        public bool StoreObject(object obj)
        {
            try
            {
                this["objectdata"].Value = HelperFunctions.SerializeToString(obj);
                this["objectvalid"] = true;
                return true;
            }
            catch
            {
                this["objectvalid"] = false;
                return false;
            }
        }
        public object GetObject()
        {
            try
            {
                if ((bool)this["objectvalid"] == true)
                {
                    return HelperFunctions.DeserializeFromString(this["objectdata"]);
                }
            }
            catch
            {
            }
            return false;
        }
        public Setting AsRoot
        {
            get
            {
                Setting s = new Setting(this.Name);
                s.Value = this.Value;
                lock (objLock)
                {
                    foreach (KeyValuePair<string, Setting> kv in this._childList)
                    {
                        s[kv.Key] = kv.Value.AsRoot;
                    }
                }
                return s;
            }
        }
        public void Save()
        {
            if (_Parent == null)
            {
                if (usecache)
                {
                    SettingsCache.WriteSetting(_SavePath, this);
                    SettingsCache.Commit(false);
                }
                FileInfo SaveFile = null;
                FileInfo BackupFile = null;
                try
                {
                    SaveFile = new FileInfo(Filename);
                    if (SaveFile.Exists)
                    {
                        BackupFile = new FileInfo(BackupPath);
                        if (BackupFile.Exists) BackupFile.Delete();
                        SaveFile.MoveTo(BackupFile.FullName);
                        BackupFile = SaveFile;
                        SaveFile = new FileInfo(Filename);
                        BackupFile.Attributes = BackupFA;
                    }
                }
                catch (FileNotFoundException)
                { }
                catch (UnauthorizedAccessException)
                { }
                catch (DirectoryNotFoundException)
                { }
                catch (DriveNotFoundException)
                { }

                XmlTextWriter XW = new XmlTextWriter(SaveFile.CreateText());
                XW.Formatting = Formatting.Indented;
                XW.WriteStartDocument();
                XW.WriteStartElement("root");
                Save(XW);
                XW.WriteEndElement();
                XW.WriteEndDocument();
                XW.Flush();
                XW.Close();
            }
            else
            {
                Root.Save();
            }
        }
        public void Clear()
        {
            lock (objLock)
            {
                foreach(KeyValuePair<string, Setting> kvp in _childList)
                {
                    kvp.Value._Parent = null;
                }
                _childList.Clear();
            }
            this._Value = "";
            WasModified(this);
        }
        public Setting Query(string Query)
        {
            return SPath.Process(this, Query);
        }
        public void Load()
        {
            if (_Parent == null)
            {
                if (usecache)
                {
                    Setting S = SettingsCache.ReadSetting(_SavePath);
                    this._childList = S._childList;
                    this._Name = S._Name;
                    this._Value = S._Value;
                    this._SavePath = S._SavePath;
                    S = null;
                    return;
                }
                FileInfo FI = new FileInfo(_SavePath);
                if (!FI.Exists)
                {
                    throw new FileNotFoundException("File not found", _SavePath);
                }
                XmlTextReader XR = new XmlTextReader(FI.OpenText());
                XmlDocument XN = new XmlDocument();
                XN.Load(XR);
                XR.Close();
                XmlNode Node = XN.SelectSingleNode("root");
                if (Node.HasChildNodes)
                {
                    Load(Node.ChildNodes[0]);
                }
                WasModified(this);
            }
            else
            {
                Root.Load();
            }
        }
        #endregion


        #region Properties
        public Setting Root
        {
            get
            {
                if (_Parent == null)
                {
                    return this;
                }
                else
                {
                    return _Parent.Root;
                }
            }
        }
        #region Default Accessor
        public Setting this[int index]
        {
            get
            {
                return this[index.ToString()];
            }
            set
            {
                this[index.ToString()] = value;
            }
        }
        public Setting this[string key]
        {
            get
            {
                if (!CheckKey(key))
                    throw new InvalidKeyException(key);
                lock (objLock)
                {
                    if (_childList.ContainsKey(key.ToLower().Trim()))
                    {
                        return _childList[key.ToLower().Trim()];
                    }
                    else
                    {
                        Setting tmp = new Setting(key, this);
                        _childList.Add(key.ToLower().Trim(), tmp);
                        return tmp;
                    }
                }
            }
            set
            {
                if (!CheckKey(key))
                    throw new InvalidKeyException(key);
                // Are we deleting this key?
                if (value == null)
                {
                    // Yes we are.
                    lock (objLock)
                    {
                        _childList[key.ToLower().Trim()]._Parent = null;
                        _childList.Remove(key.ToLower().Trim());
                    }
                    WasModified(this);
                    return;
                }
                else
                {
                    // Nope, Make sure the parent points to us before we store it
                    // In our internal dictionary.
                        value._Parent = this;
                    // we also need to make sure it has the correct name.
                    //if(value._Name != key)
                    value._Name = key;
                    
                    //WasModified(value);
                }

                if (_childList.ContainsKey(key.ToLower().Trim()))
                {
                    // We need to merge our old sub-keys with our new ones.
                    // This will prevent data loss while still maintaining
                    // The Setting[XXXX] = @"Test" functionality.
                    int vcl, ccl;
                    vcl = value._childList.Count;
                    ccl = this[key]._childList.Count;
                    if (vcl == ccl && ccl == 0)
                    {
                        if (this[key].Value != value.Value)
                            WasModified(this);
                        //Do nothing because there are no sub-keys to merge.
                    }
                    else
                    {
                        value._childList = mergeInternalDict(this[key], value);
                        WasModified(this);
                    }
                        lock (objLock)
                        {
                            _childList[key.ToLower().Trim()] = value;
                        }
                }
                else
                {
                    lock (objLock)
                    {
                        _childList.Add(key.ToLower().Trim(), value);
                        WasModified(this);
                    }
                }
                //This will fire the event IF the child key changes, otherwise we dont need
                //to manually fire this event.
                //WasModified(value);
            }

        }

        private bool CheckKey(string key)
        {
            string rex = @"^([A-Za-z0-9_ -:]+)$";
            return System.Text.RegularExpressions.Regex.IsMatch(key.Trim(), rex);
                    
        }
        public Setting this[object key]
        {
            get
            {
                return this[key.GetHashCode().ToString()];
            }
            set
            {
                this[key.GetHashCode().ToString()] = value;
            }
        }
        #endregion
        public bool CacheEnabled
        {
            get
            {
                return usecache;
            }
            set
            {
                usecache = value;
            }
        }
        public string Path
        {
            get
            {
                if (_Parent == null) return @"/" + this._Name;
                else return _Parent.Path + @"/" + this.Name;
            }
        }
        public string Name
        {
            get { return _Name; }
            set
            {
                string oldName = _Name;
                _Name = value;
                if (oldName != value)
                    WasModified(this);
            }
        }
        public string Value
        {
            get { return _Value; }
            set
            {
                string oldValue = _Value;
                _Value = value;
                if (oldValue != _Value)
                    WasModified(this);
            }
        }
        public string Filename
        {
            get
            {
                if (string.IsNullOrEmpty(_SavePath))
                    _SavePath = _Name + ".config";
                return _SavePath;
            }
            set { _SavePath = value; }
        }
        internal List<Setting> GetChildList()
        {
            List<Setting> retVal = new List<Setting>();
            lock (objLock)
            {
                foreach (KeyValuePair<string, Setting> k in _childList)
                {
                    retVal.Add(k.Value);
                }
            }
            return retVal;
        }
        public string ToString(bool ShowPath)
        {
            if (ShowPath)
            {
                return this.Path + "=" + this.Value;
            }
            else
            {
                return this.Value;
            }
        }
        public override string ToString()
        {
            return this.ToString(false);
        }
        #endregion



        #region IEnumerable<Setting> Members

        IEnumerator<Setting> IEnumerable<Setting>.GetEnumerator()
        {
            return new SettingEnumerator(this);
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new SettingEnumerator(this);
        }

        #endregion
    }
    class SettingEnumerator : IEnumerator<Setting>
    {
        private List<Setting> InternalDict;
        private int _Current = -1;
        private Setting CallingClass;
        internal SettingEnumerator(Setting toEnumerate)
        {
            CallingClass = toEnumerate;
        }

        #region IEnumerator<Setting> Members

        Setting IEnumerator<Setting>.Current
        {
            get
            {
                return GetCurrent();
            }
        }
        private Setting GetCurrent()
        {
            if (_Current == -1 || _Current >= InternalDict.Count)
                throw new InvalidOperationException("Use MoveNext before calling Current");
            return this.InternalDict[_Current];
        }
        #endregion

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            InternalDict = null;
            CallingClass = null;
        }

        #endregion

        #region IEnumerator Members

        object IEnumerator.Current
        {
            get { return GetCurrent(); }
        }

        bool IEnumerator.MoveNext()
        {
            if (_Current == -1)
            {
                InternalDict = CallingClass.GetChildList();
            }
            _Current++;
            //Is there any more data? or are we at the end?
            if (_Current >= InternalDict.Count)
                return false; // EOF ;)
            else
                return true; // Got some data :P

        }

        void IEnumerator.Reset()
        {
            _Current = -1;
        }

        #endregion
    }
}
